package br.com.marcelo.reservasis.client.util;


import br.com.aroeirasoftware.gwtcomponents.client.query.ASQuery;
import com.google.gwt.i18n.client.DateTimeFormat;
import com.google.gwt.user.client.Window;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 *
 * @author Marcelo
 */
public class QueryObject {
    private Map<String, String> selects;
    private Map<String, String> froms;
    private Map<String, String> joins;
    private Map<String, String> wheres;
    private Map<String, String> groups;
    private Map<String, String> havings;
    private Map<String, String> orders;
    private String entidade;
    private String aliasEntidade;
    private volatile Class<?> classe;

    public QueryObject(Class classe, String aliasEntidade) {
        this.classe = classe;
        this.entidade = classe.getName();
        this.aliasEntidade = aliasEntidade;
        init();
    }

    public QueryObject(Class classe) {
        this.classe = classe;
        this.entidade = classe.getName();
        this.aliasEntidade = "";
        init();
    }

    private void init() {
        selects = new LinkedHashMap<String, String>();
        froms = new LinkedHashMap<String, String>();
        joins = new LinkedHashMap<String, String>();
        wheres = new LinkedHashMap<String, String>();
        groups = new LinkedHashMap<String, String>();
        havings = new LinkedHashMap<String, String>();
        orders = new LinkedHashMap<String, String>();

        froms.put(entidade, Expressions.from(entidade, aliasEntidade));
    }

    public QueryObject addSelect(String expressao) {
        int i = expressao.indexOf(Expressions.ESPACO);

        if (i != -1) {
            selects.put(expressao.substring(0, i).trim(), expressao.trim());
        } else {
            selects.put(expressao.trim(), expressao.trim());
        }

        return this;
    }

    public QueryObject addJoin(String expressao) {
        int i = expressao.indexOf(Expressions.ESPACO);

        if (i != -1) {
            joins.put(expressao.substring(0, i).trim(), expressao.trim());
        } else {
            joins.put(expressao.trim(), expressao.trim());
        }

        return this;
    }

    public QueryObject addWhere(String expressao) {
        int i = expressao.indexOf(Expressions.ESPACO);

        if (i != -1 && expressao.length() - 1 == i) {
            wheres.remove(expressao.trim());
        } else if (i != 1 && expressao.length() - 1 != i) {
            wheres.put(expressao.substring(0, i).trim(), expressao.trim());
        } else {
            wheres.put(expressao.trim(), expressao.trim());
        }

        return this;
    }

    public QueryObject addOrder(String expressao) {
        int i = expressao.indexOf(Expressions.ESPACO);

        if (i != -1) {
            orders.put(expressao.substring(0, i).trim(), expressao.trim());
        } else {
            orders.put(expressao.trim(), expressao.trim());
        }

        return this;
    }

    public String getHQL() {
        StringBuilder sb = new StringBuilder();

        if (!selects.isEmpty()) {
            sb.append(Expressions.SELECT);
        }

        Iterator<String> is = selects.values().iterator();
        while (is.hasNext()) {
            sb.append(is.next());

            if (is.hasNext()) {
                sb.append(Expressions.VIRGULA);
            }
        }

        sb.append(Expressions.FROM);

        is = froms.values().iterator();
        while (is.hasNext()) {
            sb.append(is.next());

            if (is.hasNext()) {
                sb.append(Expressions.VIRGULA);
            }
        }

        if (!joins.isEmpty()) {
            sb.append(Expressions.ESPACO);

            is = joins.values().iterator();

            while (is.hasNext()) {
                sb.append(is.next());
            }
        }

        if (!wheres.isEmpty()) {
            sb.append(Expressions.WHERE);

            is = wheres.values().iterator();
            while (is.hasNext()) {
                sb.append(is.next());

                if (is.hasNext()) {
                    sb.append(Expressions.AND);
                }
            }
        }

        if (!groups.isEmpty()) {
            sb.append(Expressions.GROUP_BY);

            is = groups.values().iterator();
            while (is.hasNext()) {
                sb.append(is.next());

                if (is.hasNext()) {
                    sb.append(Expressions.VIRGULA);
                }
            }
        }

        if (!orders.isEmpty()) {
            sb.append(Expressions.ORDER_BY);

            is = orders.values().iterator();
            while (is.hasNext()) {
                sb.append(is.next());

                if (is.hasNext()) {
                    sb.append(Expressions.VIRGULA);
                }
            }
        }

        if (!havings.isEmpty()) {
            sb.append(Expressions.HAVING);

            is = havings.values().iterator();
            while (is.hasNext()) {
                sb.append(is.next());

                if (is.hasNext()) {
                    sb.append(Expressions.AND);
                }
            }
        }

        return sb.toString();
    }

    public String getHQLCount(String expressao) {
        StringBuilder sb = new StringBuilder();
        sb.append(Expressions.SELECT);
        sb.append(expressao);
        sb.append(Expressions.FROM);

        Iterator<String> is = froms.values().iterator();
        while (is.hasNext()) {
            sb.append(is.next());

            if (is.hasNext()) {
                sb.append(Expressions.VIRGULA);
            }
        }

        if (!joins.isEmpty()) {
            sb.append(Expressions.ESPACO);

            is = joins.values().iterator();

            while (is.hasNext()) {
                sb.append(is.next());
            }
        }

        if (!wheres.isEmpty()) {
            sb.append(Expressions.WHERE);

            is = wheres.values().iterator();
            while (is.hasNext()) {
                sb.append(is.next());

                if (is.hasNext()) {
                    sb.append(Expressions.AND);
                }
            }
        }

        if (!groups.isEmpty()) {
            sb.append(Expressions.GROUP_BY);

            is = groups.values().iterator();
            while (is.hasNext()) {
                sb.append(is.next());

                if (is.hasNext()) {
                    sb.append(Expressions.VIRGULA);
                }
            }
        }

        if (!havings.isEmpty()) {
            sb.append(Expressions.HAVING);

            is = havings.values().iterator();
            while (is.hasNext()) {
                sb.append(is.next());

                if (is.hasNext()) {
                    sb.append(Expressions.AND);
                }
            }
        }

        return sb.toString();
    }

    public ASQuery getConsulta(boolean transformarResultados) {
        return new ASQuery(classe, ASQuery.ASQueryType.HQL, getHQL(), transformarResultados);
    }

    public ASQuery getConsulta(String expressaoContagem, boolean transformarResultados, int quantidadeRegistrosPorPagina) {
        return new ASQuery(classe, ASQuery.ASQueryType.HQL, getHQL(), getHQLCount(expressaoContagem), transformarResultados, quantidadeRegistrosPorPagina);
    }

    @Override
    public String toString() {
        return getHQL();
    }

    public static class Expressions {

        private static final String AVG = "avg";
        private static final String COUNT = "count";
        private static final String DISTINCT = "distinct";
        private static final String MAX = "max";
        private static final String MIN = "min";
        private static final String LEFT_JOIN = "left  join ";
        private static final String RIGHT_JOIN = "right join ";
        private static final String INNER_JOIN = "inner join ";
        private static final String FETCH = " fetch ";
        private static final String AND = " and ";
        private static final String OR = " or ";
        private static final String LIKE = " like ";
        private static final String GT = " > ";
        private static final String GE = " >= ";
        private static final String LT = " < ";
        private static final String LE = " <= ";
        private static final String EQ = " = ";
        private static final String NE = " <> ";
        private static final String BETWEEN = " between ";
        private static final String IN = " in ";
        private static final String IS_NULL = " is null ";
        private static final String IS_NOT_NULL = " is not null ";
        private static final String EXISTS = " exists ";
        private static final String NOT_EXISTS = " not exists ";
        private static final String PARENTESE_ESQUERDO = "(";
        private static final String PARENTESE_DIREITO = ")";
        private static final String AS = " as ";
        private static final String VIRGULA = ", ";
        private static final String SUM = "sum";
        private static final String ASC = " asc";
        private static final String DESC = " desc";
        private static final String SELECT = "select ";
        private static final String FROM = " from ";
        private static final String WHERE = " where ";
        private static final String HAVING = " having ";
        private static final String GROUP_BY = " group by ";
        private static final String ORDER_BY = " order by ";
        private static final String ESPACO = " ";

        public static String distinct(String expressao) {
            return isVazio(expressao) ? ESPACO : DISTINCT + PARENTESE_ESQUERDO + expressao + PARENTESE_DIREITO;
        }

        public static String avg(String expressao, String alias) {
            return isVazio(expressao) ? ESPACO : as(AVG + PARENTESE_ESQUERDO + expressao + PARENTESE_DIREITO, alias);
        }

        public static String count(String expressao, String alias) {
            return isVazio(expressao) ? ESPACO : as(COUNT + PARENTESE_ESQUERDO + expressao + PARENTESE_DIREITO, alias);
        }

        public static String max(String expressao, String alias) {
            return isVazio(expressao) ? ESPACO : as(MAX + PARENTESE_ESQUERDO + expressao + PARENTESE_DIREITO, alias);
        }

        public static String min(String expressao, String alias) {
            return isVazio(expressao) ? ESPACO : as(MIN + PARENTESE_ESQUERDO + expressao + PARENTESE_DIREITO, alias);
        }

        public static String sum(String expressao, String alias) {
            return isVazio(expressao) ? ESPACO : as(SUM + PARENTESE_ESQUERDO + expressao + PARENTESE_DIREITO, alias);
        }

        public static String leftJoin(String expressao, String alias) {
            return isVazio(expressao) ? ESPACO : as(LEFT_JOIN + expressao, alias);
        }

        public static String rightJoin(String expressao, String alias) {
            return isVazio(expressao) ? ESPACO : as(RIGHT_JOIN + expressao, alias);
        }

        public static String innerJoin(String expressao, String alias) {
            return isVazio(expressao) ? ESPACO : as(INNER_JOIN + expressao, alias);
        }

        public static String leftJoinFetch(String expressao, String alias) {
            return isVazio(expressao) ? ESPACO : as(LEFT_JOIN + FETCH + expressao, alias);
        }

        public static String rightJoinFetch(String expressao, String alias) {
            return isVazio(expressao) ? ESPACO : as(RIGHT_JOIN + FETCH + expressao, alias);
        }

        public static String innerJoinFetch(String expressao, String alias) {
            return isVazio(expressao) ? ESPACO : as(INNER_JOIN + FETCH + expressao, alias);
        }

        public static String and(String expressao1, String expressao2) {
            return isVazio(expressao1) || isVazio(expressao2) ? ESPACO : PARENTESE_ESQUERDO + expressao1 + AND + expressao2 + PARENTESE_DIREITO;
        }

        public static String or(String expressao1, String expressao2) {
            return isVazio(expressao1) || isVazio(expressao2) ? ESPACO : PARENTESE_ESQUERDO + expressao1 + OR + expressao2 + PARENTESE_DIREITO;
        }

        public static String likeStart(String nomePropriedade, Object valor) {
            return isVazio(valor) ? nomePropriedade + ESPACO : nomePropriedade + LIKE + "'%" + valor + "'";
        }

        public static String likeEnd(String nomePropriedade, Object valor) {
            return isVazio(valor) ? nomePropriedade + ESPACO : nomePropriedade + LIKE + "'" + valor + "%'";
        }

        public static String likeFull(String nomePropriedade, Object valor) {
            return isVazio(valor) ? nomePropriedade + ESPACO : nomePropriedade + LIKE + "'%" + valor + "%'";
        }

        public static String gt(String nomePropriedade, Object valor) {
            return isVazio(valor) ? nomePropriedade + ESPACO : nomePropriedade + GT + string(valor);
        }

        public static String ge(String nomePropriedade, Object valor) {
            return isVazio(valor) ? nomePropriedade + ESPACO : nomePropriedade + GE + string(valor);
        }

        public static String lt(String nomePropriedade, Object valor) {
            return isVazio(valor) ? nomePropriedade + ESPACO : nomePropriedade + LT + string(valor);
        }

        public static String le(String nomePropriedade, Object valor) {
            return isVazio(valor) ? nomePropriedade + ESPACO : nomePropriedade + LE + string(valor);
        }

        public static String ne(String nomePropriedade, Object valor) {
            return isVazio(valor) ? nomePropriedade + ESPACO : nomePropriedade + NE + string(valor);
        }

        public static String eq(String nomePropriedade, Object valor) {
            return isVazio(valor) ? nomePropriedade + ESPACO : nomePropriedade + EQ + string(valor);
        }

        public static String between(String nomePropriedade, Object valor1, Object valor2) {
            return isVazio(valor1) || isVazio(valor2) ? nomePropriedade + ESPACO : nomePropriedade + BETWEEN + string(valor1) + AND + string(valor2);
        }

        public static String in(String nomePropriedade, Object... valor) {
            StringBuilder sb = new StringBuilder(nomePropriedade);
            sb.append(IN);
            sb.append(PARENTESE_ESQUERDO);

            for (int i = 0; i < valor.length - 1; i++) {
                if (!isVazio(valor[i])) {
                    sb.append(string(valor[i]));
                    sb.append(VIRGULA);
                }
            }

            sb.append(string(valor[valor.length - 1]));
            sb.append(PARENTESE_DIREITO);

            return sb.toString();
        }

        public static String inHQL(String nomePropriedade, Object hql) {
            return isVazio(hql) ? nomePropriedade + ESPACO : nomePropriedade + IN + PARENTESE_ESQUERDO + hql + PARENTESE_DIREITO;
        }

        public static String isNotNull(String nomePropriedade) {
            return isVazio(nomePropriedade) ? nomePropriedade + ESPACO : nomePropriedade + IS_NOT_NULL;
        }

        public static String isNull(String nomePropriedade) {
            return isVazio(nomePropriedade) ? nomePropriedade + ESPACO : nomePropriedade + IS_NULL;
        }

        public static String exists(String nomePropriedade, Object valor) {
            return isVazio(nomePropriedade) ? nomePropriedade + ESPACO : nomePropriedade + EXISTS + PARENTESE_ESQUERDO + valor + PARENTESE_DIREITO;
        }

        public static String notExists(String nomePropriedade, Object valor) {
            return isVazio(nomePropriedade) ? nomePropriedade + ESPACO : nomePropriedade + NOT_EXISTS + PARENTESE_ESQUERDO + valor + PARENTESE_DIREITO;
        }

        public static String asc(String nomePropriedade) {
            return isVazio(nomePropriedade) ? ESPACO : nomePropriedade + ASC;
        }

        public static String desc(String nomePropriedade) {
            return isVazio(nomePropriedade) ? ESPACO : nomePropriedade + DESC;
        }

        public static String as(String expressao1, String expressao2) {
            return isVazio(expressao2) ? expressao1 : expressao1 + AS + expressao2;
        }

        private static String from(String entidade, String aliasEntidade) {
            if (!isVazio(aliasEntidade)) {
                return entidade + ESPACO + aliasEntidade;
            } else {
                return entidade;
            }
        }

        private static boolean isVazio(Object expressao) {
            return (expressao == null || expressao.toString().trim().length() == 0);
        }

        private static String aspas(Object valor) {
            return "'" + valor + "'";
        }

        private static String string(Object valor) {
            if (valor instanceof String || valor instanceof Character) {
                return (aspas(valor));
            } else if (valor instanceof Date) {
                return (aspas(DateTimeFormat.getFormat("yyyy-MM-dd HH:mm:ss").format((Date) valor)));
            } else {
                return String.valueOf(valor);
            }
        }
    }

}
